﻿namespace WixBuilder
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.IO;
    using System.Linq;
    using System.Security.Cryptography;
    using System.Text;
    using System.Xml;
    using System.Xml.Linq;
    using System.Xml.XPath;

    using Microsoft.Win32;

    /// <summary>
    /// A wix definition.
    ///
    /// .wxs -> WindowsInstallerXml.wxsfile.3.8
    ///
    /// WindowsInstallerXml.wxsfile.3.8 ->
    ///
    /// HKEY_CLASSES_ROOT\WindowsInstallerXml.wxsfile.3.8\shell\Open\command ->
    ///     "C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\devenv.exe" "%1"
    ///
    /// and (better)
    ///
    /// HKEY_CLASSES_ROOT\WindowsInstallerXml.wxsfile.3.8\DefaultIcon ->
    ///     "C:\Program Files (x86)\WiX Toolset v3.8\bin\ProductFile.ico"
    /// </summary>
    public class WixDefinition : IDisposable
    {
        #region Fields

        /// <summary>
        /// Pathname of the %AppData% folder.
        ///
        /// See http://msdn.microsoft.com/en-us/library/aa367565(v=vs.85).aspx
        /// </summary>
        public const String AppDataFolder = "AppDataFolder";

        /// <summary>
        /// Pathname of the [ProductName] folder under AppDataFolder.
        /// </summary>
        public const String AppDataLocation = "ApplicationDataFolder";

        /// <summary>
        /// Pathname of the [ProductName] folder under shell:Personal (ie My Documents).
        /// </summary>
        public const String AppDocFolder = "ApplicationDocumentsFolder";

        /// <summary>
        /// Pathname of the Program Files folder.
        /// </summary>
        public const String ApplicationProgramsFolder = "ApplicationProgramsFolder";

        /// <summary>
        /// Pathname of the desktop folder.
        ///
        /// See http://msdn.microsoft.com/en-us/library/aa368276(v=vs.85).aspx
        /// </summary>The
        public const String DesktopFolder = "DesktopFolder";

        /// <summary>
        /// Pathname of the [ProductName] folder under ApplicationProgramsFolder.
        /// </summary>
        public const String InstallLocation = "INSTALLLOCATION";

        /// <summary>
        /// Pathname of the My Documents folder.
        ///
        /// See http://msdn.microsoft.com/en-us/library/aa370806(v=vs.85).aspx
        /// </summary>The
        public const String PersonalFolder = "PersonalFolder";

        /// <summary>
        /// Pathname of the The User's Menu folder.
        ///
        /// See http://msdn.microsoft.com/en-us/library/aa370882(v=vs.85).aspx
        /// </summary>
        public const String ProgramMenuFolder = "ProgramMenuFolder";

        /// <summary>
        /// The hard-coded Value for TARGETDIR.
        ///
        /// See http://msdn.microsoft.com/en-us/library/aa371857(v=vs.85).aspx
        /// </summary>
        public const String SourceDir = "SourceDir";

        /// <summary>
        /// The hard-coded Id for SourceDir.
        ///
        /// See http://msdn.microsoft.com/en-us/library/aa372064(v=vs.85).aspx
        /// </summary>
        public const String TargetDir = "TARGETDIR";

        /// <summary>
        /// The hard-coded xmlns Value for Wix.
        /// </summary>
        public readonly XNamespace xmlns = "http://schemas.microsoft.com/wix/2006/wi";

        /// <summary>
        /// The fragment wix being generated.
        /// </summary>
        public XDocument fragmentWix;

        /// <summary>
        /// The product wix being generated.
        /// </summary>
        public XDocument productWix;

        /// <summary>
        /// The Xml NameSpace Manager.
        /// </summary>
        public XmlNamespaceManager xmlnsm;

        #endregion Fields

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the WixBuilder.WixDefinition class.
        /// </summary>
        public WixDefinition()
        {
            SkipLicence = true;

            Files = new List<String>();
            Components = new List<String>();

            md5 = MD5.Create();

            Debug.Print("{0} bit(s) or {1} Byte(s)", md5.HashSize, (Int32)Math.Ceiling(md5.HashSize / 8.0));

            productWix = new XDocument();
            productWix.Declaration = new XDeclaration("1.0", "utf-8", "yes");

            fragmentWix = new XDocument();
            fragmentWix.Declaration = new XDeclaration("1.0", "utf-8", "yes");

            xmlnsm = new XmlNamespaceManager(new NameTable());
            xmlnsm.AddNamespace("wi", xmlns.ToString());

            if (Registry.ClassesRoot.OpenSubKey(".wxs") != null)
            {
                /// HKEY_CLASSES_ROOT\WindowsInstallerXml.wxsfile.3.8\DefaultIcon ->
                String dotwxs = (String)Registry.ClassesRoot.OpenSubKey(".wxs").GetValue("");
                RegistryKey rk = Registry.ClassesRoot.OpenSubKey(dotwxs);

                if (!String.IsNullOrEmpty(dotwxs) && rk != null)
                {
                    if (rk.OpenSubKey("DefaultIcon") != null)
                    {
                        String wix = ((String)rk.OpenSubKey("DefaultIcon").GetValue("")).Trim('"');
                        WiXDir = Path.GetDirectoryName(wix);
                    }
                }
            }
        }

        /// <summary>
        /// Initializes a new instance of the WixBuilder.WixDefinition class.
        /// </summary>
        ///
        /// <remarks>Path.Combine of bin and exe should yield the full path of the exe</remarks>
        ///
        /// <param name="bin"> The binaries (output) directory. </param>
        /// <param name="exe"> The main executable (without path). </param>
        public WixDefinition(String bin, String exe)
            : this()
        {
            this.MainExecutable = exe;
            this.BinariesDir = bin;

            // Make sure we get rid of a case sensitive path and executable name.
            //
            foreach (String fn in Directory.EnumerateFiles(bin, exe, SearchOption.TopDirectoryOnly))
            {
                this.BinariesDir = Path.GetDirectoryName(fn);
                this.MainExecutable = Path.GetFileName(fn);
            }

            foreach (String file in Directory.EnumerateFiles(BinariesDir, "*.*", SearchOption.AllDirectories))
            {
                Files.Add(file);
            }

            FileVersionInfo ExeVersionInfo = FileVersionInfo.GetVersionInfo(Path.Combine(bin, exe));

            CompanyName = ExeVersionInfo.CompanyName;
            ProductName = ExeVersionInfo.ProductName;
            ProductVersion = String.Format("{0}.{1}.{2}", ExeVersionInfo.ProductMajorPart, ExeVersionInfo.ProductMinorPart, ExeVersionInfo.ProductBuildPart);
            Copyright = ExeVersionInfo.LegalCopyright;
        }

        #endregion Constructors

        #region Properties

        /// <summary>
        /// Gets the binaries dir.
        /// </summary>
        ///
        /// <value>
        /// The binaries dir.
        /// </value>
        public String BinariesDir
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets or sets the name of the company.
        /// </summary>
        ///
        /// <value>
        /// The name of the company.
        /// </value>
        public string CompanyName
        {
            get;
            set;
        }

        /// <summary>
        /// Gets the components used in the installer.
        /// </summary>
        ///
        /// <value>
        /// The components.
        /// </value>
        public List<String> Components
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets or sets the copyright.
        /// </summary>
        ///
        /// <value>
        /// The copyright.
        /// </value>
        public string Copyright
        {
            get;
            set;
        }

        /// <summary>
        /// Gets the files that can be used in the installer.
        /// </summary>
        ///
        /// <value>
        /// The files.
        /// </value>
        public List<String> Files
        {
            get;
            private set;
        }

        public String InstallerFile
        {
            get
            {
#warning Hardcoded Path
                return String.Format("..\\..\\bin\\Debug\\{0} Setup v{1}.msi", ProductName, ProductVersion);
            }
        }

        /// <summary>
        /// Gets the main executable.
        /// </summary>
        ///
        /// <value>
        /// The main executable.
        /// </value>
        public String MainExecutable
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the main id.
        /// </summary>
        ///
        /// <value>
        /// The main id.
        /// </value>
        public String MainId
        {
            get
            {
                return Path.GetFileNameWithoutExtension(MainExecutable).Replace(' ', '_');
            }
        }

        /// <summary>
        /// Gets or sets the name of the product.
        /// </summary>
        ///
        /// <value>
        /// The name of the product.
        /// </value>
        public string ProductName
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the product version.
        /// </summary>
        ///
        /// <value>
        /// The product version.
        /// </value>
        public string ProductVersion
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets a value indicating whether the skip licence.
        /// </summary>
        ///
        /// <value>
        /// true if skip licence, false if not.
        /// </value>
        [DefaultValue(true)]
        public Boolean SkipLicence
        {
            get;
            set;
        }

        public WixIceCodes[] Supress
        {
            get;
            set;
        }

        /// <summary>
        /// Gets the WiX Toolkit dir.
        /// </summary>
        ///
        /// <value>
        /// The WiX Toolkit dir.
        /// </value>
        public String WiXDir
        {
            get;
            set;
        }

        /// <summary>
        /// Gets the wix variable.
        /// </summary>
        ///
        /// <value>
        /// The wix variable.
        /// </value>
        public String WixVar
        {
            get
            {
                return String.Format("$(var.{0}.TargetDir)", ProductName.Replace(' ', '_'));
            }
        }

        /// <summary>
        /// Gets or sets the MD5 Object.
        /// </summary>
        ///
        /// <value>
        /// The MD5 Instance.
        /// </value>
        private MD5 md5
        {
            get;
            set;
        }

        #endregion Properties

        #region Methods

        /// <summary>
        /// Adds a component.
        /// </summary>
        ///
        /// <remarks>
        /// Automatically created DirectoryRef of missing.
        /// </remarks>
        ///
        /// <param name="directoryRefId"> The component. </param>
        /// <param name="keyPath">        true to strip dir. </param>
        ///
        /// <returns>
        /// A String.
        /// </returns>
        public String AddComponent(String directoryRefId, Boolean keyPath = true)
        {
            XElement directoryRefs = fragmentWix.XPathSelectElement("/wi:Wix/wi:Fragment", xmlnsm);

            XElement componentRefs = fragmentWix.XPathSelectElement("/wi:Wix/wi:Fragment/wi:ComponentGroup", xmlnsm);

            XElement component = fragmentWix.XPathSelectElement(
                    String.Format("/wi:Wix/wi:Fragment/wi:DirectoryRef[@Id=\"{0}\"]", directoryRefId), xmlnsm);

            if (component == null)
            {
                directoryRefs.Add(
                    new XElement(xmlns + "DirectoryRef",
                        new Object[] {
                            new XAttribute("Id", directoryRefId),
                        })
                );

                component = fragmentWix.XPathSelectElement(
                    String.Format("/wi:Wix/wi:Fragment/wi:DirectoryRef[@Id=\"{0}\"]", directoryRefId), xmlnsm);
            }

            String ComponentGuid = String.Format("{{{0}}}", Guid.NewGuid());
            String ComponentId = GetMd5Hash(ComponentGuid, "CID_");

            if (!Components.Contains(ComponentId))
            {
                Components.Add(ComponentId);
                componentRefs.Add(
                    new XElement(xmlns + "ComponentRef",
                        new Object[] {
                                    new XAttribute("Id", ComponentId),
                                })
                    );
            }

            component.Add(
                    new XElement(xmlns + "Component",
                        new Object[] {
                                    new XAttribute("Id", ComponentId),
                                    new XAttribute("Guid", ComponentGuid),
                                        new XElement(xmlns + "RegistryValue",
                                            new Object[] {
                                                    new XAttribute("Root",      "HKCU"),
                                                    new XAttribute("Key",       String.Format("SOFTWARE\\{0}\\{1}", CompanyName, ProductName)),
                                                    new XAttribute("Type",      "string"),
                                                    new XAttribute("Name",      ComponentId),
                                                    new XAttribute("Value",     directoryRefId),
                                                    new XAttribute("KeyPath",   keyPath?"yes":"no"),
                                            }),
                                })
                    );

            return ComponentId;
        }

        /// <summary>
        /// Adds files.
        /// </summary>
        ///
        /// <param name="directoryRefId"> The component. </param>
        /// <param name="filename">       The wildcard. </param>
        /// <param name="keyPath">        true to strip dir. </param>
        ///
        /// <returns>
        /// A String.
        /// </returns>
        public String AddFile(
            String directoryRefId,
            String filename,
            Boolean stripDir,
            Boolean keyPath = false)
        {
            String asm = Path.Combine(BinariesDir, filename);

            if (!File.Exists(asm))
            {
                return String.Empty;
            }

            if (!Files.Contains(asm))
            {
                return String.Empty;
            }

            XElement directoryRefs = fragmentWix.XPathSelectElement("/wi:Wix/wi:Fragment", xmlnsm);

            XElement componentRefs = fragmentWix.XPathSelectElement("/wi:Wix/wi:Fragment/wi:ComponentGroup", xmlnsm);

            XElement directoryRef = VerifyDirectoryRef(directoryRefId);

            XElement component2 = VerifyComponent(directoryRefId, GetMd5Hash(directoryRefId, "CID_"), keyPath);

            String ComponentId = component2.Attribute("Id").Value;

            if (!Components.Contains(ComponentId))
            {
                Components.Add(ComponentId);
                componentRefs.Add(
                    new XElement(xmlns + "ComponentRef",
                        new Object[] {
                                    new XAttribute("Id", ComponentId),
                                })
                    );
            }

            Files.Remove(asm);

            if (new String[] { ".exe", ".dll" }.Contains(Path.GetExtension(asm).ToLower()))
            {
                Files.Remove(Path.ChangeExtension(asm, ".pdb"));
                Files.Remove(Path.ChangeExtension(asm, ".xml"));
                Files.Remove(asm + ".config");
            }

            String fn = Path.GetFileName(asm);
            String rfn = asm.Replace(BinariesDir, String.Empty).Trim(Path.DirectorySeparatorChar);
            String rd = rfn.Replace(Path.GetFileName(asm), String.Empty).Trim(Path.DirectorySeparatorChar);

            switch (stripDir)
            {
                case true:
                    //! Strip Complete Path (should be this or relative).

                    component2.Add(
                        new XElement(xmlns + "File",
                            new Object[] {
                        new XAttribute("Id", GetMd5Hash(asm)),
                        new XAttribute("Source", String.Format("{0}\\{1}", WixVar, rfn)),
                        new XAttribute("KeyPath", keyPath?"yes":"no"),
                    })
                    );
                    break;

                case false:
                    //! Strip Src Path (should be this or relative).
                    component2.Parent.Add(new XComment(filename));
                    component2.Parent.Add(
                        new XElement(xmlns + "Directory",
                            new Object[] {
                            new XAttribute("Id",   GetMd5Hash(asm, "DID_")),
                            new XAttribute("Name", rd),
                            new XElement(xmlns + "Component",
                                new Object[] {
                                    new XAttribute("Id",    GetMd5Hash(asm, "CID_")),
                                    new XAttribute("Guid",  String.Format("{{{0}}}", GetMd5Guid(GetMd5Hash(asm, "CID_")))),
                                    new XElement(xmlns + "File",
                                        new Object[] {
                                            new XAttribute("Id", GetMd5Hash(asm)),
                                            new XAttribute("Source", String.Format("{0}\\{1}", WixVar, rfn)),
                                        }),

                                        new XElement(xmlns + "RegistryValue",
                                            new Object[] {
                                                    new XAttribute("Root",      "HKCU"),
                                                    new XAttribute("Key",       String.Format("SOFTWARE\\{0}\\{1}", CompanyName, ProductName)),
                                                    new XAttribute("Type",      "string"),
                                                    new XAttribute("Name",      GetMd5Hash(asm)),
                                                    new XAttribute("Value",     Path.GetFileName(asm)),
                                                    //! Single files in their own component always have a registry keypath.
                                                    new XAttribute("KeyPath",   "yes"),
                                            }),
                                }),
                        })
                        );

                    if (!Components.Contains(GetMd5Hash(asm, "CID_")))
                    {
                        Components.Add(GetMd5Hash(asm, "CID_"));
                        componentRefs.Add(
                            new XElement(xmlns + "ComponentRef",
                                new Object[] {
                            new XAttribute("Id", GetMd5Hash(asm, "CID_")),
                        })
                            );
                    }

                    break;
            }

            return GetMd5Hash(asm);
        }

        /// <summary>
        /// Adds files.
        /// </summary>
        ///
        /// <remarks>
        /// Automatically created DirectoryRef of missing.
        /// </remarks>
        ///
        /// <param name="directoryRefId"> The component. </param>
        /// <param name="wildcard">  The wildcard. </param>
        /// <param name="options">   Options for controlling the operation. </param>
        /// <param name="stripDir">  true to strip dir. </param>
        public void AddFiles(
            String directoryRefId,
            String wildcard,
            SearchOption options,
            Boolean stripDir)
        {
            XElement directoryRefs = fragmentWix.XPathSelectElement("/wi:Wix/wi:Fragment", xmlnsm);

            XElement componentRefs = fragmentWix.XPathSelectElement("/wi:Wix/wi:Fragment/wi:ComponentGroup", xmlnsm);

            XElement directoryRef = VerifyDirectoryRef(directoryRefId);

            foreach (String asm in Directory.EnumerateFiles(BinariesDir, wildcard, options))
            {
                if (!Files.Contains(asm))
                {
                    continue;
                }

                Files.Remove(asm);

                if (new String[] { ".exe", ".dll" }.Contains(Path.GetExtension(asm).ToLower()))
                {
                    Files.Remove(Path.ChangeExtension(asm, ".pdb"));
                    Files.Remove(Path.ChangeExtension(asm, ".xml"));
                    Files.Remove(asm + ".config");
                }

                String fn = Path.GetFileName(asm);
                String rfn = asm.Replace(BinariesDir, String.Empty).Trim(Path.DirectorySeparatorChar);
                String rd = rfn.Replace(Path.GetFileName(asm), String.Empty).Trim(Path.DirectorySeparatorChar);

                if (stripDir)
                {
                    XElement component2 = VerifyComponent(directoryRefId, GetMd5Hash(directoryRefId, "CID_"));

                    String ComponentId = component2.Attribute("Id").Value;

                    //! Add ComponentRef is missing.
                    if (!Components.Contains(ComponentId))
                    {
                        Components.Add(ComponentId);
                        componentRefs.Add(
                            new XElement(xmlns + "ComponentRef",
                                new Object[] {
                                    new XAttribute("Id", ComponentId),
                                })
                            );
                    }

                    //! Strip Complete Path (should be this or relative).
                    component2.Add(
                             new XElement(xmlns + "File",
                                                    new Object[] {
                                                    new XAttribute("Id", GetMd5Hash(asm)),
                                                    new XAttribute("Source", String.Format("{0}\\{1}", WixVar, rfn)),
                                                })
                    );
                }
                else
                {
                    //! Strip Src Path (should be this or relative).
                    directoryRef.Add(new XComment(wildcard));
                    directoryRef.Add(
                        new XElement(xmlns + "Directory",
                            new Object[] {
                            new XAttribute("Id",   GetMd5Hash(asm, "DID_")),
                            new XAttribute("Name", rd),
                            new XElement(xmlns + "Component",
                                new Object[] {
                                    new XAttribute("Id",    GetMd5Hash(asm, "CID_")),
                                    new XAttribute("Guid",  String.Format("{{{0}}}",GetMd5Guid(GetMd5Hash(asm, "CID_")))),
                                    new XElement(xmlns + "File",
                                        new Object[] {
                                            new XAttribute("Id", GetMd5Hash(asm)),
                                            new XAttribute("Source", String.Format("{0}\\{1}", WixVar, rfn)),
                                        }),
                                        new XElement(xmlns + "RegistryValue",
                                            new Object[] {
                                                    new XAttribute("Root",      "HKCU"),
                                                    new XAttribute("Key",       String.Format("SOFTWARE\\{0}\\{1}", CompanyName, ProductName)),
                                                    new XAttribute("Type",      "string"),
                                                    new XAttribute("Name",      GetMd5Hash(asm)),
                                                    new XAttribute("Value",     Path.GetFileName(asm)),
                                                    //! Single files in their own component always have a registry keypath.
                                                    new XAttribute("KeyPath",   "yes"),
                                            }),
                                }),
                        })
                        );

                    if (!Components.Contains(GetMd5Hash(asm, "CID_")))
                    {
                        Components.Add(GetMd5Hash(asm, "CID_"));
                        componentRefs.Add(
                            new XElement(xmlns + "ComponentRef",
                                new Object[] {
                            new XAttribute("Id", GetMd5Hash(asm, "CID_")),
                            })
                        );
                    }
                }
            }
        }

        ///// <summary>
        ///// Adds a folder to 'ComponentId'.
        ///// </summary>
        /////
        ///// <param name="DirectoryId"> Identifier for the directory. </param>
        ///// <param name="ComponentId"> Identifier for the component. </param>
        //public void AddFolder(String DirectoryId, String ComponentId)
        //{
        //    XElement directoryRefs = fragmentWix.XPathSelectElement("/wi:Wix/wi:Fragment", xmlnsm);

        //    if (fragmentWix.XPathSelectElement(
        //        String.Format("/wi:Wix/wi:Fragment/wi:DirectoryRef[@Id=\"{0}\"]/wi:Component", DirectoryId), xmlnsm) == null)
        //    {
        //        directoryRefs.Add(
        //            new XElement(xmlns + "DirectoryRef",
        //                new Object[] {
        //                    new XAttribute("Id", DirectoryId),
        //                    new XElement(xmlns + "Component",
        //                        new Object[] {
        //                            new XAttribute("Id",    ComponentId),
        //                            new XAttribute("Guid",  String.Format("{{{0}}}", GetMd5Guid(ComponentId))),
        //                            new XElement(xmlns + "RegistryValue",
        //                                new Object[] {
        //                                        new XAttribute("Root",      "HKCU"),
        //                                        new XAttribute("Key",       String.Format("SOFTWARE\\{0}\\{1}", CompanyName, ProductName)),
        //                                        new XAttribute("Type",      "string"),
        //                                        new XAttribute("Name",      ComponentId),
        //                                        new XAttribute("Value",     DirectoryId),
        //                                        //! Single (sub)folders under a directoryRef, and in their own component, always have a registry keypath.
        //                                        new XAttribute("KeyPath",   "yes"),
        //                                }),
        //                        }),
        //                })
        //            );
        //    }
        //}

        public void AddShortCut(String DirectoryId,
            String ComponentId,
            String FileId,
            String WorkingDirId,
            String IonFileId,
            String Arguments,
            String LinkName)
        {
            XElement component = fragmentWix.XPathSelectElement(
                String.Format("/wi:Wix/wi:Fragment/wi:DirectoryRef[@Id=\"{0}\"]/wi:Component[@Id=\"{1}\"]", DirectoryId, ComponentId),
                xmlnsm);

            if (String.IsNullOrEmpty(Arguments))
            {
                component.Add(
                        new XComment("ShortCut"),
                            new XElement(xmlns + "Shortcut",
                                new Object[] {
                                new XAttribute("Id", GetMd5Hash(DirectoryId+ComponentId+FileId+LinkName+Arguments)),
                                new XAttribute("Name", LinkName),
                                //new XAttribute("Icon", "PMF_HtmlHelp.chm.ico"),
                                //new XAttribute("IconIndex", "0"),
                                new XAttribute("WorkingDirectory", WorkingDirId),
                                new XAttribute("Directory", DirectoryId),
                                new XAttribute("Show", "normal"),
                                new XAttribute("Target", FileId),
                                //new XElement(xmlns + "Icon",
                                //    new Object[] {
                                //        new XAttribute("Id", "PMF_HtmlHelp.chm.ico"),
                                //        new XAttribute("SourceFile",String.Format("{0}\\HtmlHelp.ico", WixVar)),
                                //    }),
                        })
                );
            }
            else
            {
                component.Add(
                    new XComment("ShortCut"),
                        new XElement(xmlns + "Shortcut",
                            new Object[] {
                            new XAttribute("Id", GetMd5Hash(DirectoryId+ComponentId+FileId+LinkName+Arguments)),
                            new XAttribute("Name", LinkName),
                            new XAttribute("WorkingDirectory", WorkingDirId),
                            new XAttribute("Directory", DirectoryId),
                            new XAttribute("Show", "normal"),
                            new XAttribute("Target", FileId),
                            new XAttribute("Arguments", Arguments),
                            })
                    );
            }
        }

        /// <summary>
        /// Adds simple user interface.
        /// </summary>
        public void AddSimpleUI()
        {
            AddSimpleUI(SkipLicence);
        }

        /// <summary>
        /// Adds simple user interface.
        /// </summary>
        ///
        /// <param name="skiplicence"> true if skip licence, false if not. </param>
        public void AddSimpleUI(Boolean skiplicence)
        {
            XElement elem = productWix.XPathSelectElement("/wi:Wix/wi:Product/wi:UI", xmlnsm);

            elem.Add(
                new Object[] {
                    new XAttribute("Id",                "WixUI_InstallDir"),

                    new XElement(xmlns + "TextStyle",
                        new Object[] {
                                new XAttribute("Id",             "WixUI_Font_Normal"),
                                new XAttribute("FaceName",      "Tahoma"),
                                new XAttribute("Size",          "8"),
                        }),
                    new XElement(xmlns + "TextStyle",
                        new Object[] {
                                new XAttribute("Id",            "WixUI_Font_Bigger"),
                                new XAttribute("FaceName",      "Tahoma"),
                                new XAttribute("Size",          "12"),
                        }),
                    new XElement(xmlns + "TextStyle",
                    new Object[] {
                            new XAttribute("Id",            "WixUI_Font_Title"),
                            new XAttribute("FaceName",      "Tahoma"),
                            new XAttribute("Size",          "9"),
                            new XAttribute("Bold",          "yes"),
                    }),

                    new XElement(xmlns + "Property",
                        new Object[] {
                                new XAttribute("Id",            "DefaultUIFont"),
                                new XAttribute("Value",         "WixUI_Font_Normal"),
                        }),

                    new XElement(xmlns + "Property",
                        new Object[] {
                                new XAttribute("Id",            "WixUI_Mode"),
                                new XAttribute("Value",         "InstallDir"),
                        }),

                    new XElement(xmlns + "DialogRef",
                        new Object[] {
                                new XAttribute("Id",            "BrowseDlg"),
                        }),
                    new XElement(xmlns + "DialogRef",
                        new Object[] {
                                new XAttribute("Id",            "DiskCostDlg"),
                        }),
                    new XElement(xmlns + "DialogRef",
                        new Object[] {
                                new XAttribute("Id",            "ErrorDlg"),
                        }),
                    new XElement(xmlns + "DialogRef",
                        new Object[] {
                                new XAttribute("Id",            "LicenseAgreementDlg"),
                        }),
                    new XElement(xmlns + "DialogRef",
                        new Object[] {
                                new XAttribute("Id",            "FatalError"),
                        }),
                    new XElement(xmlns + "DialogRef",
                        new Object[] {
                                new XAttribute("Id",            "FilesInUse"),
                        }),
                    new XElement(xmlns + "DialogRef",
                        new Object[] {
                                new XAttribute("Id",            "MsiRMFilesInUse"),
                        }),
                    new XElement(xmlns + "DialogRef",
                        new Object[] {
                                new XAttribute("Id",            "PrepareDlg"),
                        }),
                    new XElement(xmlns + "DialogRef",
                        new Object[] {
                                new XAttribute("Id",            "ProgressDlg"),
                        }),
                    new XElement(xmlns + "DialogRef",
                        new Object[] {
                                new XAttribute("Id",            "ResumeDlg"),
                    }),
                    new XElement(xmlns + "DialogRef",
                        new Object[] {
                                new XAttribute("Id",            "UserExit"),
                    }),

                    ////////////////////
                    //! 1
                    ////////////////////
                    new XComment("BrowseDlg"),

                    new XElement(xmlns + "Publish",
                        new Object[] {
                                new XAttribute("Dialog",    "BrowseDlg"),
                                new XAttribute("Control",   "OK"),
                                new XAttribute("Event",     "DoAction"),
                                new XAttribute("Value",     "WixUIValidatePath"),
                                new XAttribute("Order",     "3"),

                                new XText("1"),
                            }),

                    //! 2
                    new XElement(xmlns + "Publish",
                        new Object[] {
                            new XAttribute("Dialog",    "BrowseDlg"),
                            new XAttribute("Control",   "OK"),
                            new XAttribute("Event",     "SpawnDialog"),
                            new XAttribute("Value",     "InvalidDirDlg"),
                            new XAttribute("Order",     "4"),

                            new XCData("WIXUI_INSTALLDIR_VALID<>\"1\""),
                        }),

                    ////////////////////
                    //! 3
                    ////////////////////
                    new XComment("ExitDialog"),

                    new XElement(xmlns + "Publish",
                        new Object[] {
                            new XAttribute("Dialog",    "ExitDialog"),
                            new XAttribute("Control",   "Finish"),
                            new XAttribute("Event",     "EndDialog"),
                            new XAttribute("Value",     "Return"),
                            new XAttribute("Order",     "999"),

                            new XText("1"),
                        }),

                    ////////////////////
                    //! 4
                    ////////////////////
                    new XComment("WelcomeDlg"),

                    new XElement(xmlns + "Publish",
                        new Object[] {
                                new XAttribute("Dialog",    "WelcomeDlg"),
                                new XAttribute("Control",   "Next"),
                                new XAttribute("Event",     "NewDialog"),
                                new XAttribute("Value",     skiplicence? "InstallDirDlg" : "LicenseAgreementDlg"),

                                new XText("NOT Installed"),
                        }),

                    new XElement(xmlns + "Publish",
                        new Object[] {
                                new XAttribute("Dialog",    "WelcomeDlg"),
                                new XAttribute("Control",   "Next"),
                                new XAttribute("Event",     "NewDialog"),
                                new XAttribute("Value",     "VerifyReadyDlg"),

                                new XText("Installed AND PATCH"),
                        }),

                    ////////////////////
                    //! 5
                    ////////////////////
                    new XComment("LicenseAgreementDlg"),

                    new XElement(xmlns + "Publish",
                        new Object[] {
                                new XAttribute("Dialog",    "LicenseAgreementDlg"),
                                new XAttribute("Control",   "Back"),
                                new XAttribute("Event",     "NewDialog"),
                                new XAttribute("Value",     "WelcomeDlg"),

                                new XText("1"),
                        }),

                    new XElement(xmlns + "Publish",
                        new Object[] {
                                new XAttribute("Dialog",    "LicenseAgreementDlg"),
                                new XAttribute("Control",   "Next"),
                                new XAttribute("Event",     "NewDialog"),
                                new XAttribute("Value",     "InstallDirDlg"),

                                new XText("LicenseAccepted = \"1\""),
                        }),

                    ////////////////////
                    //! 6
                    ////////////////////
                    new XComment("InstallDirDlg"),

                    new XElement(xmlns + "Publish",
                        new Object[] {
                                new XAttribute("Dialog",    "InstallDirDlg"),
                                new XAttribute("Control",   "Back"),
                                new XAttribute("Event",     "NewDialog"),
                                new XAttribute("Value",     SkipLicence ? "WelcomeDlg" : "LicenseAgreementDlg"),

                                new XText("1"),
                            }),

                    //! 7
                    new XElement(xmlns + "Publish",
                        new Object[] {
                                new XAttribute("Dialog",    "InstallDirDlg"),
                                new XAttribute("Control",   "Next"),
                                new XAttribute("Event",     "SetTargetPath"),
                                new XAttribute("Value",     "[WIXUI_INSTALLDIR]"),
                                new XAttribute("Order",     "1"),

                                new XText("1"),
                            }),

                    //! 8
                    new XElement(xmlns + "Publish",
                        new Object[] {
                                new XAttribute("Dialog",    "InstallDirDlg"),
                                new XAttribute("Control",   "Next"),
                                new XAttribute("Event",     "DoAction"),
                                new XAttribute("Value",     "WixUIValidatePath"),
                                new XAttribute("Order",     "2"),

                                new XText("NOT WIXUI_DONTVALIDATEPATH"),
                            }),

                    //! 9
                    new XElement(xmlns + "Publish",
                        new Object[] {
                                new XAttribute("Dialog",    "InstallDirDlg"),
                                new XAttribute("Control",   "Next"),
                                new XAttribute("Event",     "SpawnDialog"),
                                new XAttribute("Value",     "InvalidDirDlg"),
                                new XAttribute("Order",     "3"),

                                new XCData("NOT WIXUI_DONTVALIDATEPATH AND WIXUI_INSTALLDIR_VALID<>\"1\""),
                            }),

                    //! 10
                    new XElement(xmlns + "Publish",
                        new Object[] {
                                new XAttribute("Dialog",    "InstallDirDlg"),
                                new XAttribute("Control",   "Next"),
                                new XAttribute("Event",     "NewDialog"),
                                new XAttribute("Value",     "VerifyReadyDlg"),
                                new XAttribute("Order",     "4"),

                                new XText("WIXUI_DONTVALIDATEPATH OR WIXUI_INSTALLDIR_VALID=\"1\""),
                            }),

                    //! 11
                    new XElement(xmlns + "Publish",
                        new Object[] {
                                new XAttribute("Dialog",    "InstallDirDlg"),
                                new XAttribute("Control",   "ChangeFolder"),
                                new XAttribute("Property",  "_BrowseProperty"),
                                new XAttribute("Value",     "[WIXUI_INSTALLDIR]"),
                                new XAttribute("Order",     "1"),

                                new XText("1"),
                            }),

                    //! 12
                    new XElement(xmlns + "Publish",
                        new Object[] {
                                new XAttribute("Dialog",    "InstallDirDlg"),
                                new XAttribute("Control",   "ChangeFolder"),
                                new XAttribute("Event",     "SpawnDialog"),
                                new XAttribute("Value",     "BrowseDlg"),
                                new XAttribute("Order",     "2"),
                                new XText("1"),
                            }),

                    ////////////////////
                    //! 13
                    ////////////////////
                    new XComment("VerifyReadyDlg"),

                    new XElement(xmlns + "Publish",
                        new Object[] {
                                new XAttribute("Dialog",    "VerifyReadyDlg"),
                                new XAttribute("Control",   "Back"),
                                new XAttribute("Event",     "NewDialog"),
                                new XAttribute("Value",     "InstallDirDlg"),
                                new XAttribute("Order",     "1"),
                                new XText("NOT Installed"),
                            }),

                    //! 14
                    new XElement(xmlns + "Publish",
                        new Object[] {
                                new XAttribute("Dialog",    "VerifyReadyDlg"),
                                new XAttribute("Control",   "Back"),
                                new XAttribute("Event",  "NewDialog"),
                                new XAttribute("Value",     "MaintenanceTypeDlg"),
                                new XAttribute("Order",     "2"),
                                new XText("Installed AND NOT PATCH"),
                            }),

                    //! 15
                    new XElement(xmlns + "Publish",
                        new Object[] {
                                new XAttribute("Dialog",    "VerifyReadyDlg"),
                                new XAttribute("Control",   "Back"),
                                new XAttribute("Event",     "NewDialog"),
                                new XAttribute("Value",     "WelcomeDlg"),
                                new XAttribute("Order",     "2"),
                                new XText("Installed AND PATCH"),
                            }),

                    ////////////////////
                    //! 16
                    ////////////////////
                    new XComment("MaintenanceWelcomeDlg"),

                    new XElement(xmlns + "Publish",
                        new Object[] {
                                new XAttribute("Dialog",    "MaintenanceWelcomeDlg"),
                                new XAttribute("Control",   "Next"),
                                new XAttribute("Event",     "NewDialog"),
                                new XAttribute("Value",     "MaintenanceTypeDlg"),
                                new XText("1"),
                            }),

                    //! 17
                    new XElement(xmlns + "Publish",
                        new Object[] {
                                new XAttribute("Dialog",    "MaintenanceTypeDlg"),
                                new XAttribute("Control",   "RepairButton"),
                                new XAttribute("Event",     "NewDialog"),
                                new XAttribute("Value",     "VerifyReadyDlg"),
                                new XText("1"),
                            }),

                    //! 18
                    new XElement(xmlns + "Publish",
                        new Object[] {
                                new XAttribute("Dialog",    "MaintenanceTypeDlg"),
                                new XAttribute("Control",   "RemoveButton"),
                                new XAttribute("Event",     "NewDialog"),
                                new XAttribute("Value",     "VerifyReadyDlg"),
                                new XText("1"),
                            }),

                    //! 19
                    new XElement(xmlns + "Publish",
                        new Object[] {
                                new XAttribute("Dialog",    "MaintenanceTypeDlg"),
                                new XAttribute("Control",   "Back"),
                                new XAttribute("Event",     "NewDialog"),
                                new XAttribute("Value",     "MaintenanceWelcomeDlg"),
                                new XText("1"),
                            }),

                    ////////////////////
                    //! 20
                    ////////////////////
                    new XElement(xmlns + "Property",
                        new Object[] {
                                    new XAttribute("Id",    "ARPNOMODIFY"),
                                    new XAttribute("Value", "1")
                        }),
                });
        }

        public void BuildFragment()
        {
            //Files.Remove(Path.Combine(BinariesDir, MainExecutable));
            //Files.Remove(Path.Combine(BinariesDir, Path.ChangeExtension(MainExecutable, ".pdb")));
            //Files.Remove(Path.Combine(BinariesDir, Path.ChangeExtension(MainExecutable, ".xml")));

            fragmentWix.Declaration = new XDeclaration("1.0", "utf-8", "yes");

            Components.Add("CID_" + MainId);
            //Components.Add("CID_AppData");
            //Components.Add("CID_Links");
            //Components.Add("CID_Uninstall");

            fragmentWix.Add(
                new XElement(xmlns + "Wix",
                    new XElement(xmlns + "Fragment",
                        new Object[] {
                            new XElement(xmlns + "DirectoryRef",
                                new Object[] {
                                    new XAttribute("Id", InstallLocation),
            //                        new XElement(xmlns + "Component",
            //                            new Object[] {
            //                                new XAttribute("Id", "CID_"+MainId),
            //                                new XAttribute("Guid", "{A84758E0-DB3C-4467-9817-A7A47410DF8A}"),

            //#warning Move into Method, ShortCuts Defineable (separate method)?

            //                                new XElement(xmlns + "File",
            //                                    new Object[] {
            //                                        new XAttribute("Id", Path.GetFileName(MainExecutable)),

            //                                        //Note: Keypad or RegistryValue but not both.
            //                                        //
            //                                        new XAttribute("KeyPath", "yes"),
            //                                        new XAttribute("Source", String.Format("{0}\\{1}", WixVar, Path.GetFileName(MainExecutable))),
            //                                    }),
                                        //}),
                                }),

                                    new XElement(xmlns + "DirectoryRef",
                                    new Object[] {
                                        new XAttribute("Id", "ApplicationProgramsFolder"),
                                    }),
                                }),

            new XElement(xmlns + "Fragment",
                new Object[] {
                    new XElement(xmlns + "ComponentGroup",
                        new Object[] {
                            new XAttribute("Id", "fragmentComponentRefGroup"),
                        }),
                    })
                )
            );
        }

        public void BuildProduct()
        {
            productWix.Declaration = new XDeclaration("1.0", "utf-8", "yes");

            String upgradeCode = "{be81632b-136c-4961-b847-8ef6fc73399f}";

            productWix.Add(
                new XElement(xmlns + "Wix",
                    new XElement(xmlns + "Product",
                        new Object[] {
            //Small updates mean small changes to one or a few files where the change doesn't warrant changing the product version (major.minor.build). You don't have to change the Product GUID, either.

            //Note that you always have to change the Package GUID when you create a new .msi file that is different from the previous ones in any respect. The Installer keeps track of your installed programs and finds them when the user wants to change or remove the installation using these GUIDs. Using the same GUID for different packages will confuse the Installer.

            //•Minor upgrades denote changes where the product version will already change. Modify the Version attribute of the Product tag. The product will remain the same, so you don't need to change the Product GUID but, of course, get a new Package GUID.
            //•Major upgrades denote significant changes like going from one full version to another. Change everything: Version attribute, Product and Package GUIDs.

            //http://www.schiffhauer.com/wix-configure-in-place-upgrades/

            #warning Generate UpgradeCode/ProductId from md5 hash of version number (or directly from it). That way it's stable until the version is bumped.

                            new XAttribute("Id",            String.Format("{{{0}}}", GetMd5Guid(ProductVersion))),
                            new XAttribute("Name",          ProductName ),
                            new XAttribute("Language",      "1033" ),
                            new XAttribute("Version",       ProductVersion ),
                            new XAttribute("Manufacturer",  CompanyName ),

            #warning Generate UpgradeCode/ProductId from md5 hash of version number (or directly from it). That way it's stable until the version is bumped.

                            new XAttribute("UpgradeCode",   upgradeCode ),

                            new XElement(xmlns + "Package",
                                new Object[] {
                                    new XAttribute("InstallerVersion",  "300"),
                                    new XAttribute("Description",       ProductName),
                                    new XAttribute("Comments",          Copyright),
                                    new XAttribute("Manufacturer",      CompanyName),
                                    new XAttribute("Languages",         "1033"),
                                    new XAttribute("InstallPrivileges", "elevated"),
                                    new XAttribute("Compressed",        "yes" ),
                                    // InstallScope="perMachine" />
                                }),
                            new XElement(xmlns + "Property",
                                new Object[] {
                                        new XAttribute("Id",   "ROOTDRIVE"),
                                        new XCData(@"C:\"),
                                }),

                            new XElement(xmlns + "Media",
                                new Object[] {
                                    new XAttribute("Id",        "1"),
                                    new XAttribute("Cabinet",   "media1.cab"),
                                    new XAttribute("EmbedCab",  "yes"),
                                }),

                            new XComment("Fixed Id and Name for single Installer Root Directory"),

                            new XElement(xmlns + "Directory",
                            new Object[] {
                                    new XAttribute("Id",       TargetDir),
                                    new XAttribute("Name",     SourceDir),

                                    new XComment("Installation Directory under Program Files"),

                                    new XElement(xmlns + "Directory",
                                        new Object[] {
                                            new XAttribute("Id", "ProgramFilesFolder"),
                                            new XElement(xmlns + "Directory",
                                                    new Object[] {
                                                        new XAttribute("Id",        CompanyName.Replace(' ','_')),
                                                        new XAttribute("Name",      CompanyName),
                                                        new XElement(xmlns + "Directory",
                                                            new Object[] {
                                                                new XAttribute("Id",        InstallLocation),
                                                                new XAttribute("Name",      ProductName),
                                                            }),
                                                    }),
                                        }),

                                    new XComment("Data Directory under %APPDATA%"),

                                    new XElement(xmlns + "Directory",
                                        new Object[] {
                                                new XAttribute("Id",       AppDataFolder),
                                                new XElement(xmlns + "Directory",
                                                    new Object[] {
                                                        new XAttribute("Id",        AppDataLocation),
                                                        new XAttribute("Name",      ProductName),
                                                    }),
                                        }),

                                    new XComment("User's Menu Folder"),

                                    new XElement(xmlns + "Directory",
                                        new Object[] {
                                                new XAttribute("Id",       ProgramMenuFolder),
                                                new XElement(xmlns + "Directory",
                                                    new Object[] {
                                                        new XAttribute("Id",        ApplicationProgramsFolder),
                                                        new XAttribute("Name",      ProductName),
                                                    }),
                                        }),

                                    new XComment("User's Desktop Folder"),

                                    new XElement(xmlns + "Directory",
                                        new Object[] {
                                                new XAttribute("Id",        DesktopFolder),
                                                //new XAttribute("Name",      "Desktop"),
                                        }),

                                    new XElement(xmlns + "Directory",
                                        new Object[] {
                                                new XAttribute("Id",        PersonalFolder),
                                                new XElement(xmlns + "Directory",
                                                    new Object[] {
                                                        new XAttribute("Id",        AppDocFolder),
                                                        new XAttribute("Name",      ProductName),
                                                    }),
                                        }),
                            }),

                            new XElement(xmlns + "Feature",
                            new Object[] {
                                    new XAttribute("Id",        "ProductFeature"),
                                    new XAttribute("Title",     ProductName),
                                    new XAttribute("Level",     "1"),
                                    new XElement(xmlns + "ComponentGroupRef",
                                        new Object[] {
                                                        new XAttribute("Id",        "fragmentComponentRefGroup"),
                                    }),
                            }),

                            new XElement(xmlns + "Property",
                                new Object[] {
                                        new XAttribute("Id",        "WIXUI_INSTALLDIR"),
                                        new XAttribute("Value",     "INSTALLLOCATION"),
                                }),

                            // Minimal minus LicenseAgreementDlg
                            //
                            new XComment("Install UI"),

                            new XElement(xmlns + "UI"),

#warning TODO Upgrade Code.
                            //new XElement(xmlns + "Upgrade",
                            //    new Object[] {
                            //        new XAttribute("Id", upgradeCode),
                            //        new XComment("VERSION UPGRADE INFO"),
                            //        new XElement(xmlns + "UpgradeVersion",
                            //            new Object[] {
                            //                new XAttribute("Maximum", ProductVersion),
                            //                new XAttribute("Property", "PREVIOUSVERSIONSINSTALLED"),
                            //            }),
                            //        new XElement(xmlns + "UpgradeVersion",
                            //            new Object[] {
                            //                new XAttribute("Minimum", ProductVersion),
                            //                new XAttribute("Property", "NEWERPRODUCTFOUND"),
                            //                new XAttribute("OnlyDetect", "yes"),
                            //                new XAttribute("IncludeMinimum", "yes"),
                            //            }),
                            //    }),

            //               Also in Feature's as ComponentRef (but not primary!)
            //<Component Id="C__746C69616E6711D38E0D00C04F6837D0" Guid="{ProductId as guid}" KeyPath="yes">
            //                                                                 ProgramMenu/ProductName
            //<RemoveFolder Id="_8969CCC8F3CC4037A0CFC4DFDE54A9CC" Directory="_F405810317604F3B9A6BFE19EF8C4634" On="uninstall" />
            //                                                                 %appdata%/ProductName
            //<RemoveFolder Id="_049A854B7B69484EAF031183D573FAB4" Directory="_6C44E2E9B4C640DC8CCC2B0F6E2B8D8F" On="uninstall" />
            //                                                                 %appdata%/ProductName/Scripts
            //<RemoveFolder Id="_3BAF0A5658A1453B8594433A080F1524" Directory="_621D6AD2B2E8499093E59700CE7102B1" On="uninstall" />
            //                                                                 %MyDocuments%/ProductName
            //<RemoveFolder Id="_D142CA17D2B3419C905CCA6ACE27B561" Directory="_BA3AA740F4444CE8B1967C61E08140D3" On="uninstall" />
            //</Component>

            //<Upgrade Id="{8E41ED61-EE6A-41E6-8543-CC475FD90621}">
            //  <UpgradeVersion Maximum="1.0.95" Property="PREVIOUSVERSIONSINSTALLED" />
            //  <UpgradeVersion Minimum="1.0.95" Property="NEWERPRODUCTFOUND" OnlyDetect="yes" IncludeMinimum="yes" />
            //</Upgrade>

                            new XElement(xmlns + "UIRef",
                                new Object[] {
                                    new XAttribute("Id",     "WixUI_Common"),
                                }),

                            new XElement(xmlns + "Property",
                                new Object[] {
                                    new XAttribute("Id", "WIXUI_EXITDIALOGOPTIONALTEXT"),
                                    new XAttribute("Value", String.Format("Thank you for installing {0}.",ProductName)),
                                }),

#warning TODO Installer Properties.

                                //<Property Id='ARPCOMMENTS'>any comments</Property>
                            //<Property Id='ARPCONTACT'>contact info</Property>
                            //<Property Id='ARPHELPLINK'>URL for technical support</Property>
                            //<Property Id='ARPURLINFOABOUT'>URL for general info about the product</Property>
                            //<Property Id='ARPURLUPDATEINFO'>URL for product updates</Property>
                            //<Property Id='ARPHELPTELEPHONE'>URL for technical support</Property>
                            //<Property Id='ARPREADME'>path</Property>
                            //<Property Id='ARPSIZE'>app size in kilobytes</Property>

                            new XElement(xmlns + "Property",
                                new Object[] {
                                    new XAttribute("Id", "ARPPRODUCTICON"),
                                    new XText(String.Format("ARP_{0}.ico",ProductName)),
                                }),

                            new XElement(xmlns + "Icon",
                                new Object[] {
                                    new XAttribute("Id", String.Format("ARP_{0}.ico",ProductName)),
                                    new XAttribute("SourceFile", String.Format("{0}\\{1}.ico", WixVar, ProductName)),
                                }),
                        })
                    )
                );
        }

        public Boolean Compile()
        {
            //! Compile
            Console.WriteLine("");
            Console.WriteLine("[Wix Compiler]");
            Console.WriteLine("");

            Process process = new Process();
            process.StartInfo = new ProcessStartInfo(
                Path.Combine(WiXDir, "candle.exe"),

                    String.Format("-d\"{0}.TargetDir={1}\" ", ProductName, BinariesDir) +
                    "-out ..\\..\\obj\\Debug\\ " +
                    "-arch x86 " +
                    "-v " +

                   String.Format("-ext \"{0}\" ", Path.Combine(WiXDir, "WixUtilExtension.dll")) +
                   String.Format("-ext \"{0}\" ", Path.Combine(WiXDir, "WixUIExtension.dll")) +

                    "Fragment.wxs " +
                    "Product.wxs")
                    {
                        UseShellExecute = false
                    };

            Debug.Print(process.StartInfo.FileName);
            Debug.Print(process.StartInfo.Arguments);

            process.Start();
            process.WaitForExit();

            Console.WriteLine("\r\nExitCode: {0}", process.ExitCode);

            return process.ExitCode == 0;
        }

        public void Dispose()
        {
            // Nothing
        }

        /// <summary>
        /// IDisposable
        /// </summary>
        void IDisposable.Dispose()
        {
            Dispose();
        }

        public Boolean Link()
        {
            //! Link
            Console.WriteLine("");
            Console.WriteLine("[Wix Linker]");
            Console.WriteLine("");

            String wixpdb = InstallerFile.Substring(0, InstallerFile.LastIndexOf('.')) + ".wixpdb";

            // See http://msdn.microsoft.com/en-us/library/aa369206(v=vs.85).aspx
            //
            String sice = String.Empty;
            foreach (WixIceCodes code in Supress)
            {
                sice += String.Format("-sice:{0} ", code);
            }

            Process process = new Process();
            process.StartInfo = new ProcessStartInfo(
               Path.Combine(WiXDir, "light.exe"),

                // "-v " +

                String.Format("-out \"{0}\" ", InstallerFile) +
                String.Format("-pdbout \"{0}\" ", wixpdb) +

                String.Format("-ext \"{0}\" ", Path.Combine(WiXDir, "WixUtilExtension.dll")) +
                String.Format("-ext \"{0}\" ", Path.Combine(WiXDir, "WixUIExtension.dll")) +

                sice +

                "..\\..\\obj\\Debug\\Fragment.wixobj " +
                "..\\..\\obj\\Debug\\Product.wixobj"
                )
            {
                UseShellExecute = false
            };

            Debug.Print(process.StartInfo.FileName);
            Debug.Print(process.StartInfo.Arguments);

            process.Start();
            process.WaitForExit();

            Console.WriteLine("ExitCode: {0}", process.ExitCode);

            return process.ExitCode == 0;
        }

        /// <summary>
        /// Print omitted files.
        /// </summary>
        public void PrintOmittedFiles()
        {
            Debug.Print("{0} Omitted File(s)", Files.Count);
            foreach (String file in Files)
            {
                Debug.Print(file);
            }
        }

        public void SaveFragment()
        {
            File.WriteAllText("Fragment.wxs", fragmentWix.Declaration.ToString() + "\r\n" + fragmentWix.ToString(SaveOptions.None));
        }

        public void SaveProduct()
        {
            File.WriteAllText("Product.wxs", productWix.Declaration.ToString() + "\r\n" + productWix.ToString(SaveOptions.None));
        }

        public Boolean Test()
        {
            //! Test
            Console.WriteLine("");
            Console.WriteLine("[Wix Test]");
            Console.WriteLine("");

            String log = Path.GetFileName(InstallerFile.Substring(0, InstallerFile.LastIndexOf('.')) + ".txt");
            String msi = Path.GetFileName(InstallerFile);

            Debug.Print("{0} {1}", @"msiexec.exe", String.Format("/i \"{0}\" /l*v \"{1}.txt\"", msi, log));

            Process process = new Process();
            process.StartInfo = new ProcessStartInfo(
                @"msiexec",

                String.Format("/i \"{0}\" /l*v \"{1}\"", msi, log)
                )
            {
                WorkingDirectory = Path.GetDirectoryName(InstallerFile),
                UseShellExecute = false
            };
            process.Start();
            process.WaitForExit();

            Console.WriteLine("ExitCode: {0}", process.ExitCode);

            return process.ExitCode == 0;
        }

        public Boolean View()
        {
            //! Test
            Console.WriteLine("");
            Console.WriteLine("[Wix View]");
            Console.WriteLine("");

            String log = Path.GetFileName(InstallerFile.Substring(0, InstallerFile.LastIndexOf('.')) + ".txt");

            Process process = new Process();
            process.StartInfo = new ProcessStartInfo('"' + log + '"')
            {
                WorkingDirectory = Path.GetDirectoryName(InstallerFile),
                UseShellExecute = true
            };
            process.Start();

            return true;
        }

        /// <summary>
        /// Gets MD5 hash.
        /// </summary>
        ///
        /// <param name="input">   The input. </param>
        ///
        /// <returns>
        /// The MD5 hash as a Guid.
        /// </returns>
        private Guid GetMd5Guid(String input)
        {
            // Convert the input string to a byte array and compute the hash.
            Byte[] data = md5.ComputeHash(Encoding.UTF8.GetBytes(input));

            return new Guid(data);
        }

        /// <summary>
        /// Gets MD5 hash.
        /// </summary>
        ///
        /// <param name="md5Hash"> The MD5 hash. </param>
        /// <param name="input">   The input. </param>
        /// <param name="prefix">  The prefix. </param>
        ///
        /// <returns>
        /// The MD5 hash.
        /// </returns>
        private String GetMd5Hash(String input, String prefix = "ID_")
        {
            // Convert the input string to a byte array and compute the hash.
            Byte[] data = md5.ComputeHash(Encoding.UTF8.GetBytes(input));

            // Create a new Stringbuilder to collect the bytes
            // and create a string.
            StringBuilder sBuilder = new StringBuilder();

            // Loop through each byte of the hashed data
            // and format each one as a hexadecimal string.
            for (Int32 i = 0; i < data.Length; i++)
            {
                sBuilder.Append(data[i].ToString("x2"));
            }

            // Return the hexadecimal string.
            return prefix + sBuilder.ToString();
        }

        /// <summary>
        /// Verify component.
        /// </summary>
        ///
        /// <param name="directoryRefId"> The component. </param>
        /// <param name="componentId">    true to strip dir. </param>
        /// <param name="keyPath">        true to strip dir. </param>
        ///
        /// <returns>
        /// An XElement.
        /// </returns>
        private XElement VerifyComponent(String directoryRefId, String componentId, Boolean keyPath = false)
        {
            XElement directoryRef = VerifyDirectoryRef(directoryRefId);

            XElement component2 = fragmentWix.XPathSelectElement(
            String.Format("/wi:Wix/wi:Fragment/wi:DirectoryRef[@Id=\"{0}\"]/wi:Component[@Id=\"{1}\"]", directoryRefId, componentId), xmlnsm);

            if (component2 == null)
            {
                switch (keyPath)
                {
                    case true:
                        directoryRef.Add(
                            new XElement(xmlns + "Component",
                                new Object[] {
                                    new XAttribute("Id",    componentId),
                                    new XAttribute("Guid",  String.Format("{{{0}}}",GetMd5Guid(componentId)))
                                })
                        );
                        break;

                    case false:
                        directoryRef.Add(
                            new XElement(xmlns + "Component",
                                new Object[] {
                                    new XAttribute("Id",    componentId),
                                    new XAttribute("Guid",  String.Format("{{{0}}}",GetMd5Guid(componentId))),
                                    new XElement(xmlns + "RegistryValue",
                                        new Object[] {
                                                new XAttribute("Root",      "HKCU"),
                                                new XAttribute("Key",       String.Format("SOFTWARE\\{0}\\{1}", CompanyName, ProductName)),
                                                new XAttribute("Type",      "string"),
                                                new XAttribute("Name",      componentId),
                                                new XAttribute("Value",     directoryRefId),
                                                new XAttribute("KeyPath",   "yes")
                                        })
                                })
                        );
                        break;
                }
            }

            return fragmentWix.XPathSelectElement(
            String.Format("/wi:Wix/wi:Fragment/wi:DirectoryRef[@Id=\"{0}\"]/wi:Component[@Id=\"{1}\"]", directoryRefId, componentId), xmlnsm);
        }

        private XElement VerifyDirectoryRef(String directoryRefId)
        {
            XElement directoryRefs = fragmentWix.XPathSelectElement("/wi:Wix/wi:Fragment", xmlnsm);

            XElement directoryRef = fragmentWix.XPathSelectElement(
                        String.Format("/wi:Wix/wi:Fragment/wi:DirectoryRef[@Id=\"{0}\"]", directoryRefId), xmlnsm);

            // Create DirectoryRef is missing
            if (directoryRef == null)
            {
                directoryRefs.Add(
                    new XElement(xmlns + "DirectoryRef",
                        new Object[] {
                            new XAttribute("Id", directoryRefId),
                        })
                );

                directoryRef = fragmentWix.XPathSelectElement(
                    String.Format("/wi:Wix/wi:Fragment/wi:DirectoryRef[@Id=\"{0}\"]", directoryRefId), xmlnsm);
            }

            return directoryRef;
        }

        /// <summary>
        /// Verify MD5 hash.
        /// </summary>
        ///
        /// <param name="input">   The input. </param>
        /// <param name="hash">    The hash. </param>
        /// <param name="prefix">  The prefix. </param>
        ///
        /// <returns>
        /// true if it succeeds, false if it fails.
        /// </returns>
        private Boolean VerifyMd5Hash(String input, String hash, String prefix = "ID_")
        {
            // Hash the input.
            String hashOfInput = GetMd5Hash(input, prefix).Replace(prefix, String.Empty);

            // Create a StringComparer an compare the hashes.
            StringComparer comparer = StringComparer.OrdinalIgnoreCase;

            if (0 == comparer.Compare(hashOfInput, hash.Replace(prefix, String.Empty)))
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        #endregion Methods
    }
}